package scrshotcombine

import (
	"github.com/carmel/gooxml/common"
	"github.com/carmel/gooxml/document"
	"github.com/carmel/gooxml/measurement"
	"github.com/carmel/gooxml/schema/soo/ofc/sharedTypes"
	"github.com/carmel/gooxml/schema/soo/wml"
	"gmap-backend/internal/util/imgutil"
	"image"
	"image/color"
	"image/draw"
	"math"
)

const (
	A4_PT_W     = 21 * 72 / 2.54
	A4_PT_H     = 29.7 * 72 / 2.54
	docx_pad_px = 20
	docx_pad_pt = float64(docx_pad_px) * float64(72) / float64(96)
)

func docxPxToPt(px int) float64 {
	return float64(px) * float64(72) / float64(96)
}

func ptToDocxPx(pt float64) int {
	return int(pt * 96 / 72)
}

// 点数转换为ST_TwipsMeasure，1英寸=1440TwipsMeasure
// https://c-rex.net/samples/ooxml/e1/Part4/OOXML_P4_DOCX_ST_TwipsMeasure_topic_ID0EDSV1B.html
func ptToST_TwipsMeasure(pt float64) uint64 {
	tmp := pt * 1440 / 72
	return uint64(tmp)
}

func saveDocx(img image.Image, imgWPx, imgHPx int, multiPage bool, savePath string) {
	if multiPage {
		saveDocxMultiPage(img, imgWPx, imgHPx, savePath)
		return
	}
	saveDocxSinglePage(img, imgWPx, imgHPx, savePath)
}

// 保存引用部分
// 由于使用单页长图的方式保存会显示不正常，所有不能像pdf一样处理，故采用图片切割的方式保存为多页
// 保存前对图片整体加左右空白（但不加上下空白，因为切割的位置不固定，需要找白色像素的行为切割位置）
// 页面大小计算，如果图片宽度未超过A4，则页面使用A4大小，否则以图片宽度和按A4宽高比缩放后的高度为页面大小，页面大小中包含上下的空白，因此计算切割位置时要排除该空白
// 当图片宽度小于页面大小时，切图时要增加左右的空白，因为inline方式不支持设置水平位置
func saveDocxMultiPage(img image.Image, imgWPx, imgHPx int, savePath string) {
	// 手动添加左右空白
	imgWPx = img.Bounds().Dx() + 2*docx_pad_px
	bgImg := image.NewRGBA(image.Rect(0, 0, imgWPx, imgHPx))
	draw.Draw(bgImg, bgImg.Bounds(), &image.Uniform{C: color.Gray16{Y: 0xFFFF}}, image.Point{}, draw.Src)
	draw.Draw(bgImg, image.Rect(20, 0, imgWPx, imgHPx), img, image.Pt(0, 0), draw.Over)
	img = bgImg

	// 计算页面大小，单位为pt
	ptW, ptH := docxPxToPt(imgWPx), docxPxToPt(imgHPx)
	pageSizeW := A4_PT_W
	pageSizeH := A4_PT_H
	paddingLR := float64(0)
	if ptW <= A4_PT_W {
		paddingLR = (A4_PT_W - ptW) / 2
	} else {
		pageSizeW = ptW
		pageSizeH = ptW * A4_PT_H / A4_PT_W
	}

	// 创建文档并设置好页面大小及上下空白
	// 其中X()使用的单位为ST_TwipsMeasure，需要转换
	// SetPageMargins使用的单位则为pt
	doc := document.New()
	twipsMeasureW := ptToST_TwipsMeasure(pageSizeW)
	twipsMeasureH := ptToST_TwipsMeasure(pageSizeH)
	doc.BodySection().X().PgSz = &wml.CT_PageSz{
		WAttr: &sharedTypes.ST_TwipsMeasure{
			ST_UnsignedDecimalNumber: &twipsMeasureW,
		},
		HAttr: &sharedTypes.ST_TwipsMeasure{
			ST_UnsignedDecimalNumber: &twipsMeasureH,
		},
	}
	doc.BodySection().SetPageMargins(measurement.Distance(docx_pad_pt), 0, measurement.Distance(docx_pad_pt), 0, 0, 0, 0)
	run := doc.AddParagraph().AddRun()
	defer run.Clear()
	defer run.ClearContent()

	// 逐个切割图片并加入文档
	y := float64(0)
	for {
		if y >= ptH {
			break
		}

		// 在保证不越界的情况下，最大可切割的高度，需要排除上下空白，pt
		availH := math.Min(pageSizeH-2*docx_pad_pt, ptH-y)

		// 先假定的切割位置及补充左右空白大小，px
		top := ptToDocxPx(y)
		bottom := ptToDocxPx(y + availH)
		pad := ptToDocxPx(paddingLR)

		// 如果不是最后一页，且能找到白色像素行的位置，则以该位置为分隔切割
		if !(availH < pageSizeH-2*docx_pad_pt) {
			blankY := determineBlankLinePos(img, imgWPx, top+(bottom-top)/2, bottom)
			if blankY >= 0 {
				bottom = blankY
				availH = docxPxToPt(bottom - top)
				//fmt.Printf("bottom is %d, blank is %d\n", bottom, blankY)
			}
		}

		// 切割图片并加入到文档
		bs := cutImg(img, top, bottom, pad)
		addInlineImg(doc, &run, bs, pageSizeW, availH)
		y += availH
	}
	doc.SaveToFile(savePath)
}

// 从指定的位置向上找白色像素的行，以作为切割的依据
// 由于屏幕截图的颜色不一定精确，白色不能以rgba值为65535为依据，适当放宽条件，当前以大于65000为匹配条件
func determineBlankLinePos(img image.Image, w, y1, y2 int) int {
	for y := y2 - 1; y >= y1; y-- {
		flag := true
		for x := 0; x < w; x++ {
			r, g, b, a := img.At(x, y).RGBA()
			if r < 65000 || g < 65000 || b < 65000 || a < 65000 {
				flag = false
				break
			}
		}
		if flag {
			return y - 2
		}
	}
	return -1
}

func addInlineImg(doc *document.Document, run *document.Run, bs []byte, w, h float64) {
	im, e := common.ImageFromBytes(bs)
	if nil != e {
		panic("图片操作时出现错误")
	}
	imgRef, e := doc.AddImage(im)
	if nil != e {
		panic("图片操作时出现错误")
	}
	imgDraw, e := run.AddDrawingInline(imgRef) // run.AddDrawingInline()
	if nil != e {
		panic("图片操作时出现错误")
	}
	imgDraw.SetSize(measurement.Distance(w), measurement.Distance(h))
}

// 从垂直方向切割图片并补充左右空白
func cutImg(img image.Image, top, bottom, pad int) []byte {
	newImgW := img.Bounds().Dx() + 2*pad
	newImgH := bottom - top

	bgImg := image.NewRGBA(image.Rect(0, 0, newImgW, newImgH))
	draw.Draw(bgImg, bgImg.Bounds(), &image.Uniform{C: color.Gray16{Y: 0xFFFF}}, image.Point{}, draw.Src)
	draw.Draw(bgImg, image.Rect(pad, 0, newImgW, newImgH), img, image.Pt(0, top), draw.Over)
	bs, err := imgutil.EncodeImgToBytes(bgImg, imgutil.FIXED_EXT_JPG)
	if nil != err {
		panic("图片操作时出现错误")
	}
	return bs
}

// 使用单页方式，如果是长图则显示会不正常，只显示一部分图片内容，但可从图片工具选择图片预览来看完整图片内容。只有docx会出现这种情况，pdf不会
func saveDocxSinglePage(img image.Image, imgWPx, imgHPx int, savePath string) {
	bs, err := imgutil.EncodeImgToBytes(img, imgutil.FIXED_EXT_JPG)
	if nil != err {
		panic("图片操作时出现错误")
	}

	// 计算所需页面大小与最终显示位置，如果不超过A4则使用A4页面大小，否则以图片本身大小为页面大小
	// 与pdf方式一致
	ptW, ptH := docxPxToPt(imgWPx), docxPxToPt(imgHPx)
	offsetX := float64(0)
	pageSizeW := A4_PT_W
	pageSizeH := A4_PT_H
	if ptW <= A4_PT_W && ptH <= A4_PT_H {
		offsetX = (A4_PT_W - ptW) / 2
	} else {
		pageSizeW = ptW
		pageSizeH = ptH
	}

	// 创建文档，添加图片并定位，保存
	doc := document.New()
	twipsMeasureW := ptToST_TwipsMeasure(pageSizeW)
	twipsMeasureH := ptToST_TwipsMeasure(pageSizeH)
	doc.BodySection().X().PgSz = &wml.CT_PageSz{
		WAttr: &sharedTypes.ST_TwipsMeasure{
			ST_UnsignedDecimalNumber: &twipsMeasureW,
		},
		HAttr: &sharedTypes.ST_TwipsMeasure{
			ST_UnsignedDecimalNumber: &twipsMeasureH,
		},
	}
	doc.BodySection().SetPageMargins(0, 0, 0, 0, 0, 0, 0)
	im, e := common.ImageFromBytes(bs)
	if nil != e {
		panic("图片操作时出现错误")
	}
	imgRef, e := doc.AddImage(im)
	if nil != e {
		panic("图片操作时出现错误")
	}
	run := doc.AddParagraph().AddRun()
	defer run.Clear()
	defer run.ClearContent()
	imgDraw, e := run.AddDrawingAnchored(imgRef) // run.AddDrawingInline()
	if nil != e {
		panic("图片操作时出现错误")
	}
	imgDraw.SetSize(measurement.Distance(ptW), measurement.Distance(ptH))
	imgDraw.SetOffset(measurement.Distance(offsetX), 0)
	doc.SaveToFile(savePath)
}
